-- 
-- Please see the readme.txt file included with this distribution for 
-- attribution and copyright information.
--

function evalAbilityHelper(rActor, sEffectAbility)
	-- PARSE EFFECT ABILITY TAG
	local sSign, sModifier, sShortAbility = string.match(sEffectAbility, "^%[([%+%-]?)([H2]?)([A-Z][A-Z][A-Z])%]$");
	
	-- FIGURE OUT WHICH ABILITY
	local nAbility = nil;
	if sShortAbility == "STR" then
		nAbility = RulesManager.getAbilityBonus(rActor, "strength");
	elseif sShortAbility == "DEX" then
		nAbility = RulesManager.getAbilityBonus(rActor, "dexterity");
	elseif sShortAbility == "CON" then
		nAbility = RulesManager.getAbilityBonus(rActor, "constitution");
	elseif sShortAbility == "INT" then
		nAbility = RulesManager.getAbilityBonus(rActor, "intelligence");
	elseif sShortAbility == "WIS" then
		nAbility = RulesManager.getAbilityBonus(rActor, "wisdom");
	elseif sShortAbility == "CHA" then
		nAbility = RulesManager.getAbilityBonus(rActor, "charisma");
	elseif sShortAbility == "LVL" then
		nAbility = RulesManager.getAbilityBonus(rActor, "level");
	end
	
	-- IF VALID SHORT ABILITY, THEN APPLY SIGN AND MODIFIERS
	if nAbility then
		if sSign == "-" then
			nAbility = 0 - nAbility;
		end
		if sModifier == "H" then
			if nAbility > 0 then
				nAbility = math.floor(nAbility / 2);
			else
				nAbility = math.ceil(nAbility / 2);
			end
		elseif sModifier == "2" then
			nAbility = nAbility * 2;
		end
	end
	
	-- RESULTS
	return nAbility;
end

function evalEffect(rActor, s)
	-- VALIDATE
	if not s then
		return "";
	end
	if not rActor then
		return s;
	end
	
	-- SETUP
	local aNewEffect = {};
	
	-- PARSE EFFECT STRING
	local aEffectComp = StringManager.split(s, ";", true);
	for keyComp, sComp in pairs(aEffectComp) do
		local aWords = StringManager.parseWords(sComp, "%[%]");
		
		if #aWords > 0 then
			if string.match(aWords[1], ":$") then
				local aTempWords = { aWords[1] };
				local nTotalMod = 0;
				
				local i = 2;
				local bAbilityFound = false;
				while aWords[i] do
					local nAbility = evalAbilityHelper(rActor, aWords[i]);
					if nAbility then
						bAbilityFound = true;
						nTotalMod = nTotalMod + nAbility;
					else
						table.insert(aTempWords, aWords[i]);
					end

					i = i + 1;
				end
				
				if StringManager.isDiceString(aTempWords[2]) then
					if nTotalMod ~= 0 then
						local aTempDice, nTempMod = StringManager.convertStringToDice(aTempWords[2]);
						nTempMod = nTempMod + nTotalMod;
						aTempWords[2] = StringManager.convertDiceToString(aTempDice, nTempMod);
					end
				elseif bAbilityFound then
					table.insert(aTempWords, 2, "" .. nTotalMod);
				end

				table.insert(aNewEffect, table.concat(aTempWords, " "));
			else
				table.insert(aNewEffect, sComp);
			end
		end
	end
	
	return table.concat(aNewEffect, "; ");
end

function getEffectsByType(node_ctentry, effecttype, aFilter, rFilterActor, bTargetedOnly)
	-- GET EFFECTS LIST
	local node_list_effects = NodeManager.createChild(node_ctentry, "effects");
	if not node_list_effects then
		return {};
	end

	-- SETUP
	local results = {};
	
	-- SEPARATE FILTERS
	local aRangeFilter = {};
	local aOtherFilter = {};
	if aFilter then
		for k, v in pairs(aFilter) do
			if StringManager.contains(DataCommon.rangetypes, v) then
				table.insert(aRangeFilter, v);
			else
				table.insert(aOtherFilter, v);
			end
		end
	end
	
	-- DETERMINE WHETHER EFFECT COMPONENT WE ARE LOOKING FOR SUPPORTS TARGETING
	local bTargetSupport = StringManager.isWord(effecttype, DataCommon.targetableeffectcomps);
	
	-- ITERATE THROUGH EFFECTS
	for k,v in pairs(node_list_effects.getChildren()) do
		-- MAKE SURE EFFECT IS ACTIVE
		if NodeManager.get(v, "isactive", 0) ~= 0 then
			-- PARSE EFFECT
			local sLabel = NodeManager.get(v, "label", "");
			local sApply = NodeManager.get(v, "apply", "");
			local effect_list = EffectsManager.parseEffect(sLabel);

			-- IF COMPONENT WE ARE LOOKING FOR SUPPORTS TARGETS, THEN GET ANY EFFECT TARGETS
			local aEffectTargets = {};
			if bTargetSupport then
				local nodeTargetList = v.getChild("targets");
				for keyTarget, nodeTarget in pairs(nodeTargetList.getChildren()) do
					table.insert(aEffectTargets, NodeManager.get(nodeTarget, "noderef", ""));
				end
			end

			-- LOOK THROUGH EFFECT CLAUSES FOR A TYPE (or TYPE/SUBTYPE) MATCH
			local nMatch = 0;
			for i = 1, #effect_list do
				
				-- CHECK FOR FOLLOWON EFFECT TAGS, AND IGNORE THE REST
				if StringManager.contains({"AFTER", "FAIL"}, effect_list[i].type) then
					break;
				end

				-- STRIP OUT ENERGY OR BONUS TYPES FOR SUBTYPE COMPARISON
				local aEffectRangeFilter = {};
				local aEffectOtherFilter = {};
				for k2, v2 in pairs(effect_list[i].remainder) do
					if StringManager.contains(DataCommon.dmgtypes, v2) then
					elseif StringManager.contains(DataCommon.bonustypes, v2) then
					elseif StringManager.contains(DataCommon.rangetypes, v2) then
						table.insert(aEffectRangeFilter, v2);
					else
						table.insert(aEffectOtherFilter, v2);
					end
				end
			
				-- CHECK TO MAKE SURE THIS COMPONENT MATCHES THE ONE WE'RE SEARCHING FOR
				local comp_match = false;
				if effect_list[i].type == effecttype then
					comp_match = true;

					-- IF EFFECT TARGETED AND THIS COMPONENT SUPPORTS TARGETING,
					-- THEN ONLY APPLY IF TARGET ACTOR MATCHES
					if (#aEffectTargets > 0) then
						comp_match = false;
						if rFilterActor and rFilterActor.nodeCT then
							for keyTarget, sTargetNode in pairs(aEffectTargets) do
								if sTargetNode == rFilterActor.sCTNode then
									comp_match = true;
								end
							end
						end
					elseif bTargetedOnly then
						comp_match = false;
					end
				
					-- CHECK THE FILTERS
					if #aEffectRangeFilter > 0 then
						local bRangeMatch = false;
						for k2, v2 in pairs(aRangeFilter) do
							if StringManager.contains(aEffectRangeFilter, v2) then
								bRangeMatch = true;
								break;
							end
						end
						if not bRangeMatch then
							comp_match = false;
						end
					end
					if #aEffectOtherFilter > 0 then
						local bOtherMatch = false;
						for k2, v2 in pairs(aOtherFilter) do
							if StringManager.contains(aEffectOtherFilter, v2) then
								bOtherMatch = true;
								break;
							end
						end
						if not bOtherMatch then
							comp_match = false;
						end
					end
				end

				-- WE FOUND A MATCH
				if comp_match then
					nMatch = i;
					table.insert(results, effect_list[i]);
				end
			end -- END EFFECT COMPONENT LOOP

			-- REMOVE ONE-SHOT EFFECTS
			if nMatch > 0 then
				if sApply == "once" then
					ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_EXPIREEFF, {node_ctentry.getNodeName(), v.getNodeName(), 0});
				elseif sApply == "single" then
					ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_EXPIREEFF, {node_ctentry.getNodeName(), v.getNodeName(), nMatch});
				end
			end
		end  -- END ACTIVE CHECK
	end  -- END EFFECT LOOP
	
	-- RESULTS
	return results;
end

function getEffectsBonusByType(node_ctentry, effecttype, flag_addemptybonus, aFilter, rFilterActor, bTargetedOnly)
	-- VALIDATE
	if not node_ctentry or not effecttype then
		return {}, 0;
	end
	
	-- MAKE BONUS TYPE INTO TABLE, IF NEEDED
	if type(effecttype) ~= "table" then
		effecttype = { effecttype };
	end
	
	-- PER EFFECT TYPE VARIABLES
	local results = {};
	local bonuses = {};
	local penalties = {};
	local nEffectCount = 0;
	
	for k, v in pairs(effecttype) do
		-- LOOK FOR EFFECTS THAT MATCH BONUSTYPE
		local effbytype = getEffectsByType(node_ctentry, v, aFilter, rFilterActor, bTargetedOnly);

		-- ITERATE THROUGH EFFECTS THAT MATCHED
		for k2,v2 in pairs(effbytype) do
			-- LOOK FOR ENERGY OR BONUS TYPES
			local energy_type = nil;
			local mod_type = nil;
			for k3, v3 in pairs(v2.remainder) do
				if StringManager.contains(DataCommon.dmgtypes, v3) then
					energy_type = v3;
					break;
				elseif StringManager.contains(DataCommon.immunetypes, v3) then
					energy_type = v3;
					break;
				elseif v3 == "all" then
					energy_type = v3;
					break;
				elseif StringManager.contains(DataCommon.bonustypes, v3) then
					mod_type = v3;
					break;
				end
			end
			
			-- IF MODIFIER TYPE IS UNTYPED OR AN ENERGY TYPE, THEN APPEND MODIFIERS
			-- (SUPPORTS DICE)
			if energy_type or not mod_type then
				-- ADD EFFECT RESULTS 
				local new_key = energy_type or "";
				local new_results = results[new_key] or {dice = {}, mod = 0};

				-- BUILD THE NEW RESULT
				for k3,v3 in pairs(v2.dice) do
					table.insert(new_results.dice, v3); 
				end
				if flag_addemptybonus then
					new_results.mod = new_results.mod + v2.mod;
				else
					new_results.mod = math.max(new_results.mod, v2.mod);
				end

				-- SET THE NEW DICE RESULTS BASED ON ENERGY TYPE
				results[new_key] = new_results;

			-- OTHERWISE, TRACK BONUSES AND PENALTIES BY MODIFIER TYPE 
			-- (IGNORE DICE, ONLY TAKE BIGGEST BONUS AND/OR PENALTY FOR EACH MODIFIER TYPE)
			else
				if v2.mod >= 0 then
					bonuses[mod_type] = math.max(v2.mod, bonuses[mod_type] or 0);
				elseif v2.mod < 0 then
					penalties[mod_type] = math.min(v2.mod, penalties[mod_type] or 0);
				end

			end
			
			-- INCREMENT EFFECT COUNT
			nEffectCount = nEffectCount + 1;
		end
	end

	-- COMBINE BONUSES AND PENALTIES FOR NON-ENERGY TYPED MODIFIERS
	for k2,v2 in pairs(bonuses) do
		if results[k2] then
			results[k2].mod = results[k2].mod + v2;
		else
			results[k2] = {dice = {}, mod = v2};
		end
	end
	for k,v in pairs(penalties) do
		if results[k2] then
			results[k2].mod = results[k2].mod + v2;
		else
			results[k2] = {dice = {}, mod = v2};
		end
	end

	-- RESULTS
	return results, nEffectCount;
end

function getEffectsBonus(node_ctentry, effecttype, modonly, aFilter, rFilterActor, bTargetedOnly)
	-- VALIDATE
	if not node_ctentry or not effecttype then
		if modonly then
			return 0, 0;
		end
		return {}, 0, 0;
	end
	
	-- MAKE BONUS TYPE INTO TABLE, IF NEEDED
	if type(effecttype) ~= "table" then
		effecttype = { effecttype };
	end
	
	-- START WITH AN EMPTY MODIFIER TOTAL
	local totaldice = {};
	local totalmod = 0;
	local nEffectCount = 0;
	
	-- ITERATE THROUGH EACH BONUS TYPE
	local masterbonuses = {};
	local masterpenalties = {};
	for k, v in pairs(effecttype) do
		-- GET THE MODIFIERS FOR THIS MODIFIER TYPE
		local effbonusbytype, nEffectSubCount = getEffectsBonusByType(node_ctentry, v, true, aFilter, rFilterActor, bTargetedOnly);
		
		-- ITERATE THROUGH THE MODIFIERS
		for k2, v2 in pairs(effbonusbytype) do
			-- IF MODIFIER TYPE IS UNTYPED OR ENERGY TYPE, THEN APPEND TO TOTAL MODIFIER
			-- (SUPPORTS DICE)
			if k2 == "" or StringManager.contains(DataCommon.dmgtypes, k2) then
				for k3, v3 in pairs(v2.dice) do
					table.insert(totaldice, v3);
				end
				totalmod = totalmod + v2.mod;
			
			-- OTHERWISE, WE HAVE A NON-ENERGY MODIFIER TYPE, WHICH MEANS WE NEED TO INTEGRATE
			-- (IGNORE DICE, ONLY TAKE BIGGEST BONUS AND/OR PENALTY FOR EACH MODIFIER TYPE)
			else
				if v2.mod >= 0 then
					masterbonuses[k2] = math.max(v2.mod, masterbonuses[k2] or 0);
				elseif v.mod < 0 then
					masterpenalties[k2] = math.min(v2.mod, masterpenalties[k2] or 0);
				end
			end
		end

		-- ADD TO EFFECT COUNT
		nEffectCount = nEffectCount + nEffectSubCount;
	end

	-- ADD INTEGRATED BONUSES AND PENALTIES FOR NON-ENERGY TYPED MODIFIERS
	for k,v in pairs(masterbonuses) do
		totalmod = totalmod + v;
	end
	for k,v in pairs(masterpenalties) do
		totalmod = totalmod + v;
	end
	
	-- RESULTS
	if modonly then
		return totalmod, nEffectCount;
	end
	return totaldice, totalmod, nEffectCount;
end

function isEffectTarget(nodeEffect, nodeTarget)
	local bMatch = false;
	
	if nodeEffect and nodeTarget then
		local nodeTargetList = nodeEffect.getChild("targets");
		if nodeTargetList then
			for k, v in pairs(nodeTargetList.getChildren()) do
				if NodeManager.get(v, "noderef", "") == nodeTarget.getNodeName() then
					bMatch = true;
					break;
				end
			end
		end
	end

	return bMatch;
end

function hasEffectCondition(nodeSourceActor, sEffect)
	return hasEffect(nodeSourceActor, sEffect, nil, false, true);
end

function hasEffect(nodeSourceActor, sEffect, nodeTargetActor, bTargetedOnly, bIgnoreEffectTargets)
	-- Parameter validation
	if not sEffect then
		return nil;
	end
	
	-- Make sure we can get to the effects list
	local node_list_effects = NodeManager.createChild(nodeSourceActor, "effects");
	if not node_list_effects then
		return nil;
	end

	-- Iterate through each effect
	local aMatch = {};
	for k,v in pairs(node_list_effects.getChildren()) do
		if NodeManager.get(v, "isactive", 0) ~= 0 then
			-- Parse each effect label
			local sLabel = NodeManager.get(v, "label", "");
			local effect_list = StringManager.split(sLabel, ";", true);
			local bTargeted = EffectsManager.isTargetedEffect(v);

			-- Iterate through each effect component looking for a type match
			local nMatch = 0;
			for i = 1, #effect_list do
				if string.sub(effect_list[i], 1, 6) == "AFTER:" or string.sub(effect_list[i], 1, 5) == "FAIL:" then
					break;
				end
				
				if string.lower(effect_list[i]) == string.lower(sEffect) then
					if bTargeted and not bIgnoreEffectTargets then
						if nodeTargetActor then
							if isEffectTarget(v, nodeTargetActor) then
								table.insert(aMatch, v);
								nMatch = i;
							end
						end
					elseif not bTargetedOnly then
						table.insert(aMatch, v);
						nMatch = i;
					end
				end
				
			end
			
			-- If matched, then remove one-off effects
			if nMatch > 0 then
				local sApply = NodeManager.get(v, "apply", "");
				if sApply == "once" then
					ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_EXPIREEFF, {nodeSourceActor.getNodeName(), v.getNodeName(), 0});
				elseif sApply == "single" then
					ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_EXPIREEFF, {nodeSourceActor.getNodeName(), v.getNodeName(), nMatch});
				end
			end
		end
	end
	
	-- Return results
	if #aMatch > 0 then
		return aMatch;
	end
	return nil;
end
